﻿/// File: ViewletControls\button.fs
/// 
/// Author: Giacomo Righetti (righetti@di.unipi.it)
///   
/// ------------------------------------------------------------
/// Copyright (c) Microsoft Corporation.  All rights reserved.
/// 
/// The use and distribution terms for this software are 
/// contained in the file named license.txt, which can be found 
/// in the root of this distribution.
/// By using this software in any fashion, you are agreeing to 
/// be bound by the terms of this license.
///
/// You must not remove this notice, or any other, from this
/// software.
/// ------------------------------------------------------------

namespace LightweightControls

open System
open System.Drawing
open System.Windows.Forms
open System.Windows.Forms.VisualStyles
  
///  
/// Summary:
///     Specifies the appearance of a control.   
type FlatStyle = 
    | Flat
    | Popup
    | Standard
    | System
    | OldStyle

///
/// Summary:
///     Provides properties that specify the appearance of LightweightControls.LightweightButton
///     controls whose LightweightControls.FlatStyle is LightweightControls.FlatStyle.Flat.
type FlatButtonAppearance (owner : LightweightButton) =

    let mutable borderColor = Color.Empty
    let mutable borderSize = 1
    let mutable checkedBackColor = Color.Empty
    let mutable mouseDownBackColor = Color.Empty
    let mutable mouseOverBackColor = Color.Empty

    let owner = owner
    
    member x.BorderColor
        with get() = borderColor
        and set(value) = 
            if value = Color.Transparent then
                raise (NotSupportedException("ButtonFlatAppearanceInvalidBorderColor"))
            else
                if borderColor <> value then
                    borderColor <- value
                    owner.Invalidate()
    
    member x.BorderSize
        with get() = borderSize
        and set(value) = 
            if value < 0 then
                raise (ArgumentOutOfRangeException("BorderSize", value, "Invalid border size value"))
            else 
                if borderSize <> value then
                    borderSize <- value
                    owner.Invalidate()
                
    member x.CheckedBackColor
        with get() = checkedBackColor
        and set(value) = 
            if checkedBackColor <> value then
                checkedBackColor <- value
                owner.Invalidate()
            
    member x.MouseDownBackColor
        with get() = mouseDownBackColor
        and set(value) = 
            if mouseDownBackColor <> value then
                mouseDownBackColor <- value
                owner.Invalidate()
  
    member x.MouseOverBackColor
        with get() = mouseOverBackColor
        and set(value) =
            if mouseOverBackColor <> value then
                mouseOverBackColor <- value
                owner.Invalidate()              

///
/// Summary:
///     The lightweight version of the button control.
and LightweightButton() as x =
    inherit LightweightControl()

    let defaultSize = Size(75,23)
            
    let mutable text = ""
    let mutable _textAlign = ContentAlignment.MiddleCenter
    let mutable foreColor = Control.DefaultForeColor
    
    let mutable _cachedAdapterType = FlatStyle.Standard
    let mutable _flatStyle = FlatStyle.Standard
    let mutable _flatAppearance: Option<FlatButtonAppearance> = None
    
    let mutable _adapter: Option<LightweightButtonBaseAdapter> = None
            
    let mutable _mouseIsOver = false
    let mutable _mouseIsDown = false
            
    let mutable _image:Image = null
    let mutable _imageAlign = ContentAlignment.MiddleCenter
                
    do
        x.Size <- defaultSize
        _flatAppearance <- Some (new FlatButtonAppearance(x))
    
    member x.Adapter
        with internal get() = 
            if _adapter.IsNone || _flatStyle <> _cachedAdapterType then
                match _flatStyle with
                | FlatStyle.Flat -> _adapter <- Some (LightweightButtonFlatAdapter(x) :> LightweightButtonBaseAdapter)
                | FlatStyle.Popup -> _adapter <- Some (LightweightButtonPopupAdapter(x) :> LightweightButtonBaseAdapter)
                | FlatStyle.Standard -> _adapter <- Some (LightweightButtonStandardAdapter(x) :> LightweightButtonBaseAdapter)
                | FlatStyle.OldStyle -> _adapter <- Some (LightweightButtonOldStyleAdapter(x) :> LightweightButtonBaseAdapter)
                
                // FIX: lookup the current system style
                | FlatStyle.System -> _adapter <- Some (LightweightButtonStandardAdapter(x) :> LightweightButtonBaseAdapter)
                
            _cachedAdapterType <- _flatStyle
            _adapter.Value
            
    member internal x.MouseIsDown 
        with get() = _mouseIsDown
                
    member internal x.MouseIsOver 
        with get() = _mouseIsOver
                       
    member private x.paintControl (e:PaintEventArgs) = 
        x.Adapter.Paint(e)
                    
    member private x.resetImage() = 
        _image <- null
    
    ///        
    /// Summary:
    ///     Gets or sets the text associated with this control.
    ///
    // Returns:
    ///     The text associated with this control.
    member x.Text
        with get() = text
        and set(t) = 
            text <- t
            base.Invalidate()
            
    ///
    /// Summary:
    ///     Gets or sets the alignment of the text on the button control.
    ///
    /// Returns:
    ///     One of the System.Drawing.ContentAlignment values. The default is MiddleCenter.
    member x.TextAlign
        with get() = _textAlign
        and set(value) =
            _textAlign <- value
            base.Invalidate()
    
    ///
    /// Summary:
    ///     Gets or sets the foreground color of the control.
    ///
    /// Returns:
    ///     The foreground System.Drawing.Color of the control. The default is the value
    ///     of the System.Windows.Forms.Control.DefaultForeColor property.
    member x.ForeColor
        with get() = foreColor
        and set(c) = 
            foreColor <- c
            base.Invalidate()
    
    ///
    /// Summary:
    ///     Gets or sets the flat style appearance of the button control.
    ///
    /// Returns:
    ///     One of the LightweightControls.FlatStyle values. The default value is Standard.
    member x.FlatStyle
        with get() = _flatStyle
        and set(value) = 
            _flatStyle <- value
            base.Invalidate()
    
    ///
    /// Summary:
    ///     Gets the appearance of the border and the colors used to indicate check state
    ///     and mouse state.
    member x.FlatAppearance
        with get() = _flatAppearance.Value
    
    ///
    /// Summary:
    ///     Gets or sets the image that is displayed on a button control.
    ///
    /// Returns:
    ///     The System.Drawing.Image displayed on the button control. The default value
    ///     is null.
    member x.Image
        with get() = _image
        and set(value) = 
            _image <- value
            base.Invalidate()
    
    ///
    /// Summary:
    ///     Gets or sets the alignment of the image on the button control.
    ///
    /// Returns:
    ///     One of the System.Drawing.ContentAlignment values. The default value is MiddleCenter.
    member x.ImageAlign
        with get() = _imageAlign
        and set(value) =
            _imageAlign <- value
            base.Invalidate()
    
    ///
    /// Summary:
    ///     Generates a Click event for a button.        
    member x.PerformClick() = x.OnClick(EventArgs.Empty)
              
    override x.OnKeyDown (e:KeyEventArgs) =
        if e.KeyCode = Keys.Space then
            if not _mouseIsDown then
                _mouseIsDown <- true
                base.Invalidate()
            e.Handled <- true
        elif e.KeyCode = Keys.Enter || e.KeyCode = Keys.Return then
            x.OnClick(EventArgs.Empty)
            e.Handled <- true
        base.OnKeyDown(e)
        
    override x.OnKeyUp (e:KeyEventArgs) = 
        if _mouseIsDown then
            _mouseIsDown <- false
            base.Invalidate()            
            if e.KeyCode = Keys.Return || e.KeyCode = Keys.Space then
                x.OnClick(EventArgs.Empty)
                e.Handled <- true            
        base.OnKeyUp(e)
        
    override x.OnGotFocus (e:EventArgs) =
        base.OnGotFocus(e)
        base.Invalidate()
   
    override x.OnLostFocus (e:EventArgs) =
        base.OnLostFocus(e)
        _mouseIsDown <- false
        base.Invalidate()
            
    override x.OnMouseUp(e) = 
        _mouseIsDown <- false
        base.Invalidate()
        base.OnMouseDown(e)
                                   
    override x.OnMouseDown(e) =
        if e.Button = MouseButtons.Left then
            _mouseIsDown <- true
            base.Invalidate()
        base.OnMouseDown(e)

    override x.OnMouseEnter(e) =
        _mouseIsOver <- true
        base.Invalidate()
        base.OnMouseEnter(e)
                    
    override x.OnMouseLeave(e) =
        _mouseIsOver <- false
        base.Invalidate()
        base.OnMouseLeave(e)
   
    override x.OnPaint(e:PaintEventArgs) = 
        x.paintControl(e)
 
 and

  ///
  /// Summary:
  ///     The abstract base class for all button adapeter. 
  ///     Instances of classes derived from this are responsible for painting the button.
  [<AbstractClass>] 
  internal LightweightButtonBaseAdapter (button : LightweightButton) =
    
    let mutable control = button

    let calcLeftTopPoint (alignment:System.Drawing.ContentAlignment, w, h) =
        match alignment with
            | ContentAlignment.TopLeft -> 
                PointF(3.0f, 3.0f)
            | ContentAlignment.TopCenter -> 
                PointF((float32 button.Width - w) / 2.0f, 3.0f)
            | ContentAlignment.TopRight -> 
                PointF(float32 button.Width - w - 2.0f, 3.0f)
            | ContentAlignment.MiddleLeft -> 
                PointF(3.0f, (float32 button.Height - h) / 2.0f)
            | ContentAlignment.MiddleCenter -> 
                PointF((float32 button.Width - w) / 2.0f, (float32 button.Height - h) / 2.0f)
            | ContentAlignment.MiddleRight ->
                PointF(float32 button.Width - float32 w - 3.0f, (float32 button.Height - h) / 2.0f)
            | ContentAlignment.BottomLeft ->
                PointF(3.0f, float32 button.Height - h - 3.0f)
            | ContentAlignment.BottomCenter ->
                PointF((float32 button.Width - w) / 2.0f, float32 button.Height - h - 3.0f)
            | ContentAlignment.BottomRight ->
                PointF(float32 button.Width - w - 3.0f, float32 button.Height - h - 3.0f)
            | _ -> raise (System.ArgumentException())
    
    member x.Control with get() = control     
            
    member internal x.Paint(e:PaintEventArgs) =
        let g = e.Graphics
        if control.Enabled then
            if control.MouseIsDown then 
                x.PaintDown(g, CheckState.Unchecked)      
            elif control.MouseIsOver then
                x.PaintOver(g, CheckState.Unchecked)
            else
                x.PaintUp(g, CheckState.Unchecked)
            if button.Image <> null then
                x.drawImage(g)
        else
            x.PaintDisabled(g)        
        x.paintText(g)
    
    /// Draws the button image. It assumes that the image is not null.
    member private x.drawImage(g:Graphics) =
        let spt = calcLeftTopPoint(button.ImageAlign, float32 button.Image.Width, float32 button.Image.Height)    
        g.DrawImage(button.Image, spt)
    
    /// This method is intended to be "protected".
    /// NB: do not call from outside of a LightweightButtonAdapter.
    abstract paintText: Graphics -> unit
    default x.paintText (g:Graphics) =
        use bfg = if button.Enabled then new SolidBrush(button.ForeColor) else new SolidBrush(SystemColors.GrayText)
        let szf = g.MeasureString(button.Text, button.Font)
        let spt = calcLeftTopPoint(button.TextAlign, szf.Width, szf.Height)
        // FIX: this seems not to be working: maybe there is some error in the graphic context setup. 
        //      Some strange artifacts appears ..
        // TextRenderer.DrawText(g, button.Text, button.Font, button.Bounds, Color.Black, TextFormatFlags.WordBreak)        
        g.DrawString(button.Text, button.Font, bfg, spt)
    
    abstract PaintDown: g:Graphics * state:CheckState -> unit
    abstract PaintOver: g:Graphics * state:CheckState -> unit
    abstract PaintUp: g:Graphics * state:CheckState -> unit
    abstract PaintDisabled: g:Graphics -> unit

///
/// Summary:
///     Renders a button with flat style.
and internal LightweightButtonFlatAdapter (button : LightweightButton) =
    inherit LightweightButtonBaseAdapter (button)
        
    member private x.drawBorder (g, borderSize) =
        ControlPaint.DrawBorder(g, button.ClientRectangle, 
            button.FlatAppearance.BorderColor, borderSize, ButtonBorderStyle.Solid, 
            button.FlatAppearance.BorderColor, borderSize, ButtonBorderStyle.Solid, 
            button.FlatAppearance.BorderColor, borderSize, ButtonBorderStyle.Solid,
            button.FlatAppearance.BorderColor, borderSize, ButtonBorderStyle.Solid)    
    
    member private x.drawFocusRectangle (g) =
        let sz = button.FlatAppearance.BorderSize
        let focusRect = Rectangle(sz + 4, sz + 4, button.Width - sz*2 - 8, button.Height - sz*2 - 8)
        ControlPaint.DrawBorder(g, focusRect, SystemColors.ButtonShadow, ButtonBorderStyle.Solid)
                
    override x.PaintDown(g, state:CheckState) =
        use brush = new SolidBrush(button.FlatAppearance.MouseDownBackColor)
        g.FillRectangle(brush, button.ClientRectangle)
        x.drawBorder(g, button.FlatAppearance.BorderSize + 1)
        if button.Container.Value.ShowTabIndexedControl then
            x.drawFocusRectangle(g)
        
    override x.PaintOver(g, state:CheckState) = 
        use brush = new SolidBrush(button.FlatAppearance.MouseOverBackColor)
        g.FillRectangle(brush, button.ClientRectangle)
        if button.Focused then
            x.drawBorder(g, button.FlatAppearance.BorderSize + 1)
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
        else
            x.drawBorder(g, button.FlatAppearance.BorderSize)
        
    override x.PaintUp(g, state:CheckState) = 
        if button.Focused then
            x.drawBorder(g, button.FlatAppearance.BorderSize + 1)
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
        else
            x.drawBorder(g, button.FlatAppearance.BorderSize)
            
    override x.PaintDisabled(g) =
        if button.FlatAppearance.BorderColor <> Color.Empty then
            x.drawBorder (g, button.FlatAppearance.BorderSize)
        else
            g.DrawRectangle(Pens.Black, button.ClientRectangle)
             
///
/// Summary:
///     Renders a button with popup style.
and internal LightweightButtonPopupAdapter (button : LightweightButton) =
    inherit LightweightButtonBaseAdapter (button)
    
    member private x.drawFocusRectangle (g) =
        let focusRect = Rectangle(4, 4, button.Width - 8, button.Height - 8)
        ControlPaint.DrawFocusRectangle(g, focusRect)
    
    member private x.drawBorderFocused (g, leftTopSize) =
        ControlPaint.DrawBorder(g, button.ClientRectangle,
            SystemColors.ButtonShadow, leftTopSize, ButtonBorderStyle.Solid,
            SystemColors.ButtonShadow, leftTopSize, ButtonBorderStyle.Solid,
            SystemColors.ButtonShadow, 2, ButtonBorderStyle.Solid,
            SystemColors.ButtonShadow, 2, ButtonBorderStyle.Solid)
    
    override x.paintText (g:Graphics) =
        if button.MouseIsDown then 
            use bfg = new SolidBrush(button.ForeColor)
            let szf = g.MeasureString(button.Text, button.Font)
            // FIX: let spt = calcLeftTopPoint(button.TextAlign, szf.Width, szf.Height)
            // calcLeftTopPoint should be visibile! Maybe make an helper class ..
            let spt = PointF(((float32 button.Width - szf.Width) / 2.0f) + 1.0f, ((float32 button.Height - szf.Height) / 2.0f) + 1.0f)
            g.DrawString(button.Text, button.Font, bfg, spt)
        else 
            base.paintText(g)
    
    override x.PaintDown(g, state:CheckState) =
        x.drawBorderFocused(g, 2)
        ControlPaint.DrawBorder(g, button.ClientRectangle, Color.Black, ButtonBorderStyle.Solid)
        if button.Container.Value.ShowTabIndexedControl then
            x.drawFocusRectangle(g)
        
    override x.PaintOver(g, state:CheckState) =
        if button.Focused then
            x.drawBorderFocused(g, 1)
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
        else
            ControlPaint.DrawBorder3D(g, button.ClientRectangle, Border3DStyle.RaisedInner)
                        
    override x.PaintUp(g, state:CheckState) = 
        if button.Focused then
            x.drawBorderFocused(g, 2)
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
        else
            ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Flat)
    
    override x.PaintDisabled(g) =
        ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Flat)
    
///
/// Summary:
///     Renders a button with standard style.
and internal LightweightButtonStandardAdapter (button : LightweightButton) =
    inherit LightweightButtonBaseAdapter (button)
    
    override x.PaintDown(g, state:CheckState) =
        let focus = button.Container.Value.ShowTabIndexedControl
        ButtonRenderer.DrawButton(g, button.ClientRectangle, focus, PushButtonState.Pressed)
        
    override x.PaintOver(g, state:CheckState) = 
        let focus = button.Focused && button.Container.Value.ShowTabIndexedControl
        ButtonRenderer.DrawButton(g, button.ClientRectangle, focus, PushButtonState.Hot)
                
    override x.PaintUp(g, state:CheckState) = 
        if button.Focused then
            let focus = button.Container.Value.ShowTabIndexedControl
            ButtonRenderer.DrawButton(g, button.ClientRectangle, focus, PushButtonState.Default)
        else
            ButtonRenderer.DrawButton(g, button.ClientRectangle, false, PushButtonState.Normal)
            
    override x.PaintDisabled(g) =
        ButtonRenderer.DrawButton(g, button.ClientRectangle, PushButtonState.Disabled)
            
///
/// Summary:
///     Renders a button with an old style.
and internal LightweightButtonOldStyleAdapter (button : LightweightButton) =
    inherit LightweightButtonBaseAdapter (button)
    
    member private x.drawFocusRectangle (g) =
        let focusRect = Rectangle(4, 4, button.Width - 8, button.Height - 8)
        ControlPaint.DrawFocusRectangle(g, focusRect)
    
    override x.paintText (g:Graphics) =
        if button.MouseIsDown then 
            use bfg = new SolidBrush(button.ForeColor)
            let szf = g.MeasureString(button.Text, button.Font)
            // FIX: let spt = calcLeftTopPoint(button.TextAlign, szf.Width, szf.Height)
            // calcLeftTopPoint should be visibile! Maybe make an helper class ..
            let spt = PointF(((float32 button.Width - szf.Width) / 2.0f) + 1.0f, ((float32 button.Height - szf.Height) / 2.0f) + 1.0f)
            g.DrawString(button.Text, button.Font, bfg, spt)
        else 
            base.paintText(g)
    
    override x.PaintDown(g, state:CheckState) =
        ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Pushed)
        if button.Container.Value.ShowTabIndexedControl then
            x.drawFocusRectangle(g)
            
    override x.PaintOver(g, state:CheckState) = 
        ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Normal)
        if button.Focused then
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
                
    override x.PaintUp(g, state:CheckState) = 
        ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Normal)            
        if button.Focused then
            if button.Container.Value.ShowTabIndexedControl then
                x.drawFocusRectangle(g)
    
    override x.PaintDisabled(g) =
        ControlPaint.DrawButton(g, button.ClientRectangle, ButtonState.Normal)